import { Client, ClientChannel, ClientErrorExtensions, SFTPWrapper } from 'ssh2'
import { exec, execSync } from 'child_process'
import fs from "fs";
// 部署相关
import _conf from "./config/deploy.json";
import _package from "./package.json";

// 服务器部署内容
interface SSHConnectConf {
    host: string,  //主机ip
    port: number,                //SSH 连接端口
    username: string,        //用户名
    password: string,     //用户登录密码
}


// 连接服务器
class AutoDeployer {
    private connect: Client;

    // 远端服务器根目录地址
    private remoteRootPath: string = ""
    // 远端服务器项目地址
    private remoteServerPATH: string = "";
    // 远端上传打包文件地址
    private remotePackage: string = "";
    // 远端备份项目地址
    private remoteBackPath: string = "";

    constructor() {
        const me = this;
        const { name, version } = _package;
        const { ssh_rootPath, ssh_backupDir, ssh_nodeDir } = _conf;
        let time = me.createDeployTime();
        me.remoteRootPath = `${ssh_rootPath}`;
        // 服务压缩包
        me.remotePackage = `${me.remoteRootPath}/dist.tar.gz`;
        // 生成完整的远端项目地址
        me.remoteServerPATH = `${ssh_rootPath}/${ssh_nodeDir}/${name}`
        // 生成备份时间
        me.remoteBackPath = `${ssh_rootPath}/${ssh_backupDir}/${name}-v${version}-${time}`
    }

    // 连接服务器
    connectSSH(serverConfig: SSHConnectConf) {
        var me = this;
        console.log(me.remoteRootPath, '-----开始连接远端服务器');
        me.connect = new Client();
        me.connect.on('ready', () => {
            console.log(me.remoteRootPath, '-----远端服务器连接就绪')
            me.uploadPackageFile(me);
        }).on('error', function (e: ClientErrorExtensions) {
            me.connectFailedCb(e);
        }).connect(serverConfig);
    }

    // connect error event
    connectFailedCb(context: ClientErrorExtensions) {
        console.log('ssh操作失败失败!', context)
    }

    // when connect 
    uploadPackageFile(context: AutoDeployer) {
        console.log(context.remotePackage, '-----打包文件上传开始!')
        context.connect.sftp((err: Error | undefined, sftp: SFTPWrapper) => {
            if (err) {
                console.log("获取上传信息失败!")
                return
            }
            sftp.fastPut('./dist.tar.gz', `${context.remotePackage}`, {}, () => {
                console.log(context.remotePackage, '-----打包文件上传成功!');
                let cmd = `
                npm cache clean -f
                cd ${context.remoteRootPath}
                mkdir ${_conf.ssh_backupDir} 
                mkdir ${_conf.ssh_nodeDir}
                mv ${context.remoteServerPATH} ${context.remoteBackPath}
                tar zxvf dist.tar.gz
                mv dist ${context.remoteServerPATH}
                mv ${context.remoteBackPath}/node_modules ${context.remoteServerPATH}/node_modules
                rm -rf dist.tar.gz
                cd ${context.remoteServerPATH}
                pm2 delete ${_package.name}
                ${_conf.installed ? `\n` : "pnpm i"}
                pm2 start src/main.js --name ${_package.name} -i 2
                exit 
                `;

                // 相关执行步骤如下:
                // * cd 到 linux操作系统根目录
                // * 创建备份文件夹
                // * 备份项目文件
                // * 备份文件夹的node_modules
                // * 解压打包文件
                // * 解压后文件命名移动到指定项目文件夹
                // * 进入到项目文件的src目录
                // * 使用pnpm i 安装依赖（减少下载请求,同版本的包实现复用）
                // * 杀死某个服务进程
                // * 清除缓存
                // * pm2 开启服务  这边可根据服务器的情况进行负载均衡
                // * 退出命令

                context.handleLinuxShell(cmd, context);
            })
        })
    }

    // handle shell
    handleLinuxShell(cmdCode: string, context: AutoDeployer) {
        console.log(`======> ${_package.name} 服务器环境部署开始`);
        // open remote server device shell
        context.connect.shell(function (err: Error | undefined, stream: ClientChannel) {
            if (err !== undefined) {
                console.log('remoteShell------操作失败')
                return;
            }
            stream.on('error', function (e: any) {
                console.log('remoteShell------操作失败', e)
            }).on('data', function (data: Buffer) {
                let message = data.toString()
                if (message.indexOf('pnpm i') !== -1) {
                    if (message.indexOf('exit') !== -1) return;
                    console.log(`======> ${_package.name} 项目依赖开始下载中...`);
                    console.log('tips: =====> 首次依赖安装时间较久,请耐心等待.....')
                } else if (message.indexOf('Done in') !== -1) {
                    console.log(`======> ${_package.name} 项目依赖下载完成!`);
                    context.updateConfiguration();
                } else if (message.indexOf('Done.') !== -1) {
                    console.log(`======> ${_package.name} 项目部署成功!`, `${_conf.host}:${context.getAppPort()} 可访问`);
                    context.updateConfiguration();
                }
                else {
                    // console.log(message)
                }
            }).on('close', function () {
                context.connect.end();
                console.log(context.remoteRootPath, '-----ssh连接已断开');
                console.timeEnd('auto-deploy')
            }).stderr.on('data', function (data: any) {
                console.log('stderr: ' + data)
            })
            stream.end(cmdCode)
        })
    }

    // 
    createDeployTime() {
        let dt = new Date();
        let y = dt.getFullYear(),
            m = (dt.getMonth() + 1 + "").padStart(2, "0"),
            d = (dt.getDate() + "").padStart(2, "0"),
            hh = (dt.getHours() + "").padStart(2, "0"),
            mm = (dt.getMinutes() + "").padStart(2, "0"),
            ss = (dt.getSeconds() + "").padStart(2, "0");
        return `${y}-${m}$-${d}_${hh}:${mm}:${ss}`
    }

    updateConfiguration() {
        let content = { ..._conf };
        content.installed = true;
        content.serverIds = "";
        fs.writeFileSync("./config/deploy.json", JSON.stringify(content), "utf-8")
        console.log('config/deploy.json', "-----文件已更新")
        console.log(this.remoteRootPath, '-----自动化部署完成!')
    }

    getAppPort(): number {
        const data: string[] = fs.readFileSync('.env.production').toString()?.split('\n');
        const portRow = data.find(item => item.indexOf('APP_PORT') !== -1);
        return portRow === undefined ? 3000 : (parseInt(portRow.replace(/\D/g, '')) || 3000);
    }
}

console.time('auto-deploy')
console.log('开始前准备------文件包开始压缩!')
let pro = exec('tar.sh', () => { })
pro.stdout.pipe(process.stdout);
pro.on('exit', () => {
    console.log('开始前准备------文件包压缩完成!')
    new AutoDeployer().connectSSH({
        host: _conf.host,
        username: _conf.username,
        port: _conf.port,
        password: _conf.password
    });
})